//TODO:original version had ignore-error feature

#include "ceventmanager.h"
#include "croot.h"
#include "cclient.h"
#include "cdecoration.h"
#include "cmanagedclient.h"

#include <buola/gui/x11/cx11toolkit.h>
#include <buola/gui/x11/cx11display.h>

namespace buola { namespace bwm {

static const char *sBackgroundProps[] =
{
    "_XROOTPMAP_ID",
    "_XSETROOT_ID",
    nullptr,
};

CEventManager::CEventManager()
{
}

CEventManager::~CEventManager()
{
}

void CEventManager::Start()
{
    gui::x11::get_x11_toolkit()->GetDispatcher()->SetOptions(gui::x11::CX11Dispatcher::OPTION_PROCESSALL);
    gui::x11::get_x11_toolkit()->GetDispatcher()->AddFilter(this);
    gui::x11::get_x11_toolkit()->GetDispatcher()->AddDamageFilter(this);
}

void CEventManager::SelectManagerEvents(CClient *pClient)
{
    XSelectInput(gui::x11::get_default_x11_display()->GetDisplay(),pClient->mID,
            EnterWindowMask|LeaveWindowMask|
            PropertyChangeMask|FocusChangeMask);
}

void CEventManager::SelectComposerEvents(CClient *pClient)
{
    XShapeSelectInput(gui::x11::get_default_x11_display()->GetDisplay(),pClient->mID,
            ShapeNotifyMask);
}

void CEventManager::DeselectManagerEvents(CClient *pClient)
{
    XSelectInput(gui::x11::get_default_x11_display()->GetDisplay(),pClient->mID,None);
}

void CEventManager::DeselectComposerEvents(CClient *pClient)
{
    XShapeSelectInput(gui::x11::get_default_x11_display()->GetDisplay(),pClient->mID,None);
}

bool CEventManager::DoMapRequest(XMapRequestEvent *pE)
{
    if(pE->parent==gRoot->GetID())
    {
        //we don't reparent windows anymore, so every time a window wants to be mapped, we'll end up here
        //only children of the root window should be managed
        CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

        if(!lClient)
        {
            lClient=dynamic_cast<CManagedClient*>(gRoot->CreateClientFor(pE->window,false));

            if(!lClient)
                return false;
        }

        if(lClient)
            lClient->OnMapRequest();
    }
    return false;
}

bool CEventManager::DoConfigureRequest(XConfigureRequestEvent *pE)
{
    CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

    if(lClient)
    {
        lClient->OnConfigureRequest(pE);
        return false;
    }

    if(pE->parent==gRoot->GetID())
    {
        //The window is a child of root... which has never been mapped
        //We just do the same configure, and will process when it is mapped
        XWindowChanges lWC;

        pE->value_mask &= ~CWSibling;

        lWC.x = pE->x;
        lWC.y = pE->y;
        lWC.width  = pE->width;
        lWC.height = pE->height;
        lWC.border_width = 0;
        lWC.sibling = None;
        lWC.stack_mode = Above;
        pE->value_mask &= ~CWStackMode;
        pE->value_mask |= CWBorderWidth;

        XConfigureWindow(gui::x11::get_default_x11_display()->GetDisplay(), pE->window, pE->value_mask, &lWC);
    }
    return false;
}

bool CEventManager::DoMapNotify(XMapEvent *pE)
{
    if(pE->event==gRoot->GetID())
    {
        if(pE->window==gRoot->GetID())
        {
            msg_warn() << "map notify for root... should probably never happen\n";
            return false;
        }

        CClient *lClient=gRoot->GetClient(pE->window);

        if(!lClient)
        {
            //this is either an override redirect window, or one of our own windows (for example, a decoration)
            //in the second case, CreateClientFor will return nullptr, since we want to ignore it
            lClient=gRoot->CreateClientFor(pE->window,false);

            if(!lClient)
                return false;

            gRoot->mClientsStack.push_back(lClient);
            lClient->mStackingOrder=(1<<29);
        }

        gRoot->WMRestack();
        lClient->OnMapNotify(false);

        return false;
    }

    return true;
}

bool CEventManager::DoUnmapNotify(XUnmapEvent *pE)
{
    if(pE->event==gRoot->GetID())
    {
        CClient *lU=gRoot->GetClient(pE->window);
        if(lU)
        {
            lU->OnUnmapNotify(pE->send_event);
        }
        return false;
    }

    return true;
}

bool CEventManager::DoCirculateNotify(XCirculateEvent *pE)
{
    return false;
}

bool CEventManager::DoPropertyNotify(XPropertyEvent *pE)
{
    if(pE->window==gRoot->GetID())
    {
        for(int p = 0; sBackgroundProps[p]; p++)
        {
            if (pE->atom == XInternAtom(
                            gui::x11::get_default_x11_display()->GetDisplay(),
                            sBackgroundProps[p], False))
            {
                gRoot->OnBackgroundPropChanged();
                return false;
            }
        }

        if(pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetActiveWindow||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetClientList||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetSupported||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetClientListStacking||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetCurrentDesktop||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetDesktopGeometry||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetDesktopViewport||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetNumberOfDesktops||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetWorkArea||
           pE->atom==gui::x11::get_default_x11_display()->Atoms().mNetDesktopNames)
        {
        //We changed this property... do nothing
            return false;
        }
        else
        {
            msg_debug<5>() << "Property " << gui::x11::get_default_x11_display()->GetAtomName(pE->atom) << " changed in root\n";
            return false;
        }
    }
    else
    {
        CManagedClient *lC=gRoot->GetManagedClient(pE->window);

        if(lC)
            lC->OnPropertyNotify(pE);
    }

    return true;
}

bool CEventManager::DoReparentNotify(XReparentEvent *pE)
{
    if(pE->event==gRoot->GetID())
    {
        if (pE->parent != gRoot->GetID()) //reparent out
        {
            CClient *lU=gRoot->GetClient(pE->window);
            if(lU)
                lU->OnClientReparented();
        }
        return false;
    }

    return true;
}

bool CEventManager::DoConfigureNotify(XConfigureEvent *pE)
{
    if(pE->window==gRoot->GetID())
    {
        gRoot->OnRootConfigure();
        return false;
    }
    else
    {
        CClient *lC=gRoot->GetClient(pE->window);
        
        if(lC)
            lC->OnConfigureNotify(pE);
    }

    return true;
}

bool CEventManager::DoDestroyNotify(XDestroyWindowEvent *pE)
{
    CClient *lClient=gRoot->GetClient(pE->window);

    if(lClient)
    {
        lClient->OnDestroyNotify();
    }

    return true;
}

bool CEventManager::DoExpose(XExposeEvent *pE)
{
    if (pE->window==gRoot->GetID())
    {
        gRoot->OnRootExpose(pE);
        return false;
    }
    else
    {
        CDecoration *lDecoration=gRoot->GetDecoration(pE->window);
        if(lDecoration)
        {
            lDecoration->OnDecorationExpose(pE);
            return false;
        }
    }
    return true;
}

bool CEventManager::DoButtonPress(XButtonEvent *pE)
{
    CPoint_d lPoint(pE->x_root,pE->y_root);
    gui::EMouseButton lButton=gui::x11::CX11Dispatcher::TranslateButtonFromX(pE->button);
    int lMods=gui::x11::CX11Toolkit::GetMods(pE->state);

    CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

    if(lClient)
    {
        lClient->OnButtonDown(lPoint,lButton,lMods);
        return true;
    }

    CDecoration *lDecoration=gRoot->GetDecoration(pE->window);

    if(lDecoration)
    {
        lDecoration->OnButtonDown(lPoint,lButton,lMods);
        return false;
    }

    return true;
}

bool CEventManager::DoButtonRelease(XButtonEvent *pE)
{
    CPoint_d lPoint(pE->x_root,pE->y_root);
    gui::EMouseButton lButton=gui::x11::CX11Dispatcher::TranslateButtonFromX(pE->button);
    int lMods=gui::x11::CX11Toolkit::GetMods(pE->state);

    CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

    if(lClient)
    {
        lClient->OnButtonUp(lPoint,lButton,lMods);
        return true;
    }

    CDecoration *lDecoration=gRoot->GetDecoration(pE->window);

    if(lDecoration)
    {
        lDecoration->OnButtonUp(lPoint,lButton,lMods);
        return false;
    }

    return true;
}

bool CEventManager::DoMotionNotify(XMotionEvent *pE)
{
    CPoint_d lPoint(pE->x_root,pE->y_root);

    CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

    if(lClient)
    {
        lClient->OnMouseMove(lPoint);
        return true;
    }

    CDecoration *lDecoration=gRoot->GetDecoration(pE->window);

    if(lDecoration)
    {
        lDecoration->OnMouseMove(lPoint);
        return false;
    }

    return true;
}

bool CEventManager::DoLeaveNotify(XCrossingEvent *pE)
{
    CDecoration *lDecoration=gRoot->GetDecoration(pE->window);

    if(lDecoration)
    {
        lDecoration->OnMouseOut();
        return false;
    }

    return true;
}

bool CEventManager::DoClientMessage(XClientMessageEvent *pE)
{
    if(pE->window==gRoot->GetID())
    {
        if(pE->message_type==gui::x11::get_default_x11_display()->Atoms().mNetNumberOfDesktops)
        {
            while(pE->data.l[0]>gRoot->mDesktops.mCount)
            {
                gRoot->CreateDesktop(L"new desktop");
            }
        }
        else if(pE->message_type==gui::x11::get_default_x11_display()->Atoms().mNetCurrentDesktop)
        {
            gRoot->SwitchToDesktop(pE->data.l[0],true);
        }
        else
        {
            msg_debug<5>() << "Root ClientMessage:" <<
            gui::x11::get_default_x11_display()->GetAtomName(pE->message_type) << "\n";
        }
    }
    else
    {
        CManagedClient *lClient=gRoot->GetManagedClient(pE->window);

        if(lClient)
        {
            lClient->OnClientMessage(pE);
        }
    }
    return true;
}

bool CEventManager::DoShapeEvent(XShapeEvent *pE)
{
    CClient *lClient=gRoot->GetClient(pE->window);

    if(lClient)
    {
        lClient->OnShapeChanged();
    }

    return false;
}

bool CEventManager::DoDamageEvent(XDamageNotifyEvent *pE)
{
    CClient *lU=gRoot->GetClient(pE->drawable);
    if(lU)
    {
        lU->OnDamage(CRect_i(CPoint_i(pE->area.x,pE->area.y),
                    CSize_i(pE->area.width,pE->area.height)));
    }
    else
    {
        msg_warn() << "can't find window to dispatch damage for " << pE->drawable << "\n";
    }
    return false;
}

bool CEventManager::OnX11Event(XEvent *pE)
{
    bool lReturn=true;

    switch(pE->xany.type)
    {
    case MapRequest:
        lReturn=DoMapRequest(&pE->xmaprequest);
        break;
    case ConfigureRequest:
        lReturn=DoConfigureRequest(&pE->xconfigurerequest);
        break;
    case PropertyNotify:
        lReturn=DoPropertyNotify(&pE->xproperty);
        break;
    case ReparentNotify:
        lReturn=DoReparentNotify(&pE->xreparent);
        break;
    case ConfigureNotify:
        lReturn=DoConfigureNotify(&pE->xconfigure);
        break;
    case DestroyNotify:
        lReturn=DoDestroyNotify(&pE->xdestroywindow);
        break;
    case MapNotify:
        lReturn=DoMapNotify(&pE->xmap);
        break;
    case UnmapNotify:
        lReturn=DoUnmapNotify(&pE->xunmap);
        break;
    case CirculateNotify:
        lReturn=DoCirculateNotify(&pE->xcirculate);
        break;
    case Expose:
        lReturn=DoExpose(&pE->xexpose);
        break;
    case ClientMessage:
        lReturn=DoClientMessage(&pE->xclient);
        break;
    case ButtonPress:
        lReturn=DoButtonPress(&pE->xbutton);
        break;
    case ButtonRelease:
        lReturn=DoButtonRelease(&pE->xbutton);
        break;
    case LeaveNotify:
        lReturn=DoLeaveNotify(&pE->xcrossing);
        break;
    case MotionNotify:
        lReturn=DoMotionNotify(&pE->xmotion);
        break;
    default:
        if(pE->xany.type==gui::x11::get_x11_toolkit()->mShapeEvent+ShapeNotify)
        {
            lReturn=DoShapeEvent((XShapeEvent*)pE);
        }
        else if(pE->xany.type==gui::x11::get_x11_toolkit()->mDamageEvent+XDamageNotify)
        {
            lReturn=DoDamageEvent((XDamageNotifyEvent*)pE);
        }
    }

    return lReturn;
}

bool CEventManager::OnX11BadWindow(Window pID)
{
    msg_error() << "Bad window received for " << pID << "\n";
    return false;
}

/*namespace bwm*/ } /*namespace buola*/ }
